package com.bnzj.core.persistence.query;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.persistence.Entity;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

/**
 * 查询帮助类
 * @author barry
 *
 */
//查询组件结果集映射，一般放到JPA的类上(实体类或者@Repository注解的类或接口)
//@formatter:off
//@SqlResultSetMappings(
//可以有很多个映射，entities，classes，columns可以都使用，返回的也是数组
//@SqlResultSetMapping(
//  name = "SysUser.AllFields",//名字在项目中必须全局唯一
//  entities = {
      //结果集的实体映射，如果映射实体，不如直接使用QueryHelpBean.entityClass进行实体映射
      //缺点：每个字段都需要手动映射，查询返回的列可以多，但是不能少
      //@EntityResult(entityClass = SysUser.class, fields = {@FieldResult(name = "id", column = "id")})
//  },
//  classes = {
      //构造函数映射，可以不是实体
      //缺点：必须要全参的构造函数，且顺序不能变，不知道返回的列可不可以少
      //@ConstructorResult(targetClass = SysUser.class, columns = {@ColumnResult(name = "id")})
//  },
//  columns = {
      //列映射，没什么用，返回的是数组
//      @ColumnResult(name = "id")
//  }
//)
//)
//@formatter:on
public class QueryHelpBean {
	
    //基础的查询SQL
	private String baseSQL;

    //基础的查询总数SQL
	private String baseCountSQL;
	
	//返回结果集映射(实体的类注解@SqlResultSetMapping的name)，和entityClass互斥
	//如果为空，默认返回List<Map<String, Object>>类型
	private String resultSetMapping;
    
    //返回结果集映射到类，和resultSetMapping互斥
    //如果为空，默认返回List<Map<String, Object>>类型
	//使用实体类映射时，查询返回的列可以多，但是不能少
    private Class<?> mappingClass;
    
    //标志是否映射到实体
    private boolean mappingClassIsEntity;

    //最终生成的条件SQL
    private String generatedConditionSQL;
    
    private String generatedPageSql;
    
    private String generatedSortSql;

    //是否已生成
    private boolean generated;
	
	//存放参数
	private final Map<String, Object> paramsMap = new HashMap<String, Object>();
	
	//分页对象
	private Pageable pageable;
	
	//分组表达式
	private String groupStr;
	
	//group by 过滤条件
	private volatile IExpression havingExpression;//volatile禁止重排序且get操作时，始终获取最新值
    
    //过滤条件
	private volatile IExpression whereExpression;//volatile禁止重排序且get操作时，始终获取最新值
    
	//如果resultSetMapping和mappingClass都为空，则返回List<Map>
    public QueryHelpBean(String baseSQL, String baseCountSQL) {
        this.baseSQL = baseSQL;
        this.baseCountSQL = baseCountSQL;
    }

    //如果resultSetMapping为空，mappingClass不为空，则使用mappingClass
    public QueryHelpBean(String baseSQL, String baseCountSQL, Class<?> mappingClass) {
        this.baseSQL = baseSQL;
        this.baseCountSQL = baseCountSQL;
        setMappingClass(mappingClass);
    }

    //如果resultSetMapping为空，mappingClass不为空，则使用mappingClass，可以强制设置mappingClassIsEntity的值
	public QueryHelpBean(String baseSQL, String baseCountSQL, Class<?> mappingClass, boolean mappingClassIsEntity) {
		this.baseSQL = baseSQL;
		this.baseCountSQL = baseCountSQL;
        setMappingClass(mappingClass);
        this.mappingClassIsEntity = mappingClassIsEntity;
	}

    //如果resultSetMapping不为空，则优先使用resultSetMapping
    public QueryHelpBean(String baseSQL, String baseCountSQL, String resultSetMapping) {
        this.baseSQL = baseSQL;
        this.baseCountSQL = baseCountSQL;
        this.resultSetMapping = resultSetMapping;
    }
    
    public void reGenerate() {
        generated = false;
        generate();
    }
    
    public void generate() {
        generateConditionSQL();
        generatePageSql();
        generateSortSql();
        generated = true;
    }
	
	private void generateConditionSQL(){
		StringBuilder sql = new StringBuilder();
		formatCondition(sql, whereExpression);
		if(sql.length() > 0) {
		    sql.insert(0, " " + whereExpression.getOperator().name() + " ");
		}
        if(StringUtils.isNotEmpty(groupStr)) {
        	sql.append(" group by ").append(groupStr);
            formatCondition(sql, havingExpression);
        }
        generatedConditionSQL = sql.toString();
	}
    
    public void generatePageSql() {
        if(pageable != null && pageable.isPaged()) {
            generatedPageSql = getPageSql(pageable.getPageNumber(), pageable.getPageSize());
        }else {
            generatedPageSql = "";
        }
    }
	
    public void generateSortSql() {
        if(pageable != null) {
            generatedSortSql = getSortSql(pageable.getSort());
        }else {
            generatedSortSql = "";
        }
    }

    private void formatCondition(StringBuilder sql, IExpression expression){
        if (expression != null) {
            String compareSql = expression.buildSQL(paramsMap);
            sql.append(compareSql);
        }
    }
	
	public static String getSortSql(Sort sort) {
		StringBuilder orderStr = new StringBuilder();
		if (sort != null && sort.isSorted()) {
			orderStr.append(" order by ");
			Iterator<Sort.Order> iterator = sort.iterator();
			while (iterator.hasNext()) {
				Sort.Order order = iterator.next();
				orderStr.append(order.getProperty()).append(" ").append(order.getDirection().name()).append(", ");
			}
			orderStr.deleteCharAt(orderStr.length() - 2);
		}
		return orderStr.toString();
	}
	
	public static String getPageSql(int pageNum, int pageSize) {
		if (pageNum < 0) {
			pageNum = 0;
		}
		int startNo = pageNum * pageSize;
		String pageStr = " limit " + startNo + ", " + pageSize;
		return pageStr;
	}

	public static QueryHelpBean clone(QueryHelpBean bean) {
		if(bean == null) {
			return null;
		}
		try {
			return (QueryHelpBean) bean.clone();
		} catch (CloneNotSupportedException e) {
			return null;
		}
	}

	public String getBaseSQL() {
		return baseSQL;
	}
	
	public void setBaseSQL(String baseSQL) {
		this.baseSQL = baseSQL;
	}

    public String getBaseCountSQL() {
        return baseCountSQL;
    }

    public void setBaseCountSQL(String baseCountSQL) {
        this.baseCountSQL = baseCountSQL;
    }

    public String getResultSetMapping() {
        return resultSetMapping;
    }

    public void setResultSetMapping(String resultSetMapping) {
        this.resultSetMapping = resultSetMapping;
        this.mappingClass = null;
        this.mappingClassIsEntity = false;
    }
    
    public boolean isMappingClassIsEntity() {
        return mappingClassIsEntity;
    }

    @SuppressWarnings("unchecked")
    public <T> Class<T> getMappingClass() {
        return (Class<T>)mappingClass;
    }

    public void setMappingClass(Class<?> mappingClass) {
        this.mappingClass = mappingClass;
        if(mappingClass == null) {
            this.mappingClassIsEntity = false;
            return;
        }
        //如果类上有Entity的注解，则是实体
        if(mappingClass.isAnnotationPresent(Entity.class)) {
            this.mappingClassIsEntity = true;
        }else {
            this.mappingClassIsEntity = false;
        }
        this.resultSetMapping = null;
    }

    public String getQuerySQL() {
        return baseSQL + generatedConditionSQL + generatedSortSql + generatedPageSql;
    }

    public String getExportSQL() {
        return baseSQL + generatedConditionSQL + generatedSortSql;
    }

	public Map<String, Object> getParamsMap() {
		return paramsMap;
	}

	public String getBuildCountSQL() {
	    if(StringUtils.isNotEmpty(groupStr)) {
	        //如果是分组查询，需要包含查询总数
	        return "select count(1) from (" + baseCountSQL + generatedConditionSQL + ") b";
	    }else {
	        return baseCountSQL + generatedConditionSQL;
	    }
	}

	public Pageable getPageable() {
		return pageable;
	}

	public void setPageable(Pageable pageable) {
		this.pageable = pageable;
	}

	public String getGroupStr() {
		return groupStr;
	}

	public void setGroupStr(String groupStr) {
		this.groupStr = groupStr;
	}
	
	public void addParameter(String key, Object value) {
		paramsMap.put(key, value);
	}

    public void addWhere(IExpression... expression) {
        int index = 0;
        if (whereExpression == null) {
            synchronized (this) {
                if (whereExpression == null) {
                    whereExpression = expression[0];
                    index = 1;
                }
            }
        }
        expression = ArrayUtils.subarray(expression, index, expression.length);
        whereExpression = whereExpression.and(expression);
    }

	public void addHaving(IExpression... expression) {
        int index = 0;
        if (havingExpression == null) {
            synchronized (this) {
                if (havingExpression == null) {
                    havingExpression = expression[0];
                    index = 1;
                }
            }
        }
        expression = ArrayUtils.subarray(expression, index, expression.length);
        havingExpression = havingExpression.and(expression);
	}

    /**
     * @return the generated
     */
    public boolean isGenerated() {
        return generated;
    }
}
